﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Comunication;
using ServerDomain.CommandFolder;
using ServerDomain.Properties;
using Utiles;

namespace ServerDomain
{
    public class TcpPacketProcessor : ITcpPacketProcessor
    {
        private byte[] _currentBuffer = new byte[0];
        private FileStream _fileContent;
        private String _fileName;
        private String _md5;
        public Socket SocketConnection;
        internal int Bandwith;
        internal int paqueteActual;
        internal int totalPaquetes;
        public ServerStates EstadoActual;
        int fileLength;
        internal bool actionFileUpload;
        internal bool ServerSync = false;
        internal bool uploadedOK = false;


        #region AutoResetEvents
        object lockServers = new object();
        private IDictionary<string, ServerEventsDTO> servers = new Dictionary<string, ServerEventsDTO>();


        private ServerEventsDTO AddServer(IPEndPoint key, Socket socket)
        {
            lock (lockServers)
            {
                ServerEventsDTO serverEventsDTO;
                string stringKey = string.Format("{0}:{1}", key.Address.ToString(), key.Port);
                if (!servers.TryGetValue(stringKey, out serverEventsDTO))
                {
                    serverEventsDTO = new ServerEventsDTO();
                    serverEventsDTO.AutoResetEvent = new AutoResetEvent(false);
                    serverEventsDTO.Socket = socket;
                    servers.Add(stringKey, serverEventsDTO );
                }
                else
                {
                    serverEventsDTO.AutoResetEvent.Reset();
                }
                return serverEventsDTO;
            }
        }

        private void SetServer(IPEndPoint key)
        {
            lock (lockServers)
            {
                ServerEventsDTO serverEventsDTO;
                string stringKey = string.Format("{0}:{1}", key.Address.ToString(), key.Port);
                if (servers.TryGetValue(stringKey, out serverEventsDTO))
                {
                    serverEventsDTO.AutoResetEvent.Set();
                }
            }
        }

        private ServerEventsDTO GetServer(IPEndPoint key)
        {
            string stringKey = string.Format("{0}:{1}", key.Address.ToString(), key.Port);
            lock (lockServers)
            {
                return servers[stringKey];
            }
        }

        public AutoResetEvent[] GetServersAutoResetEvents()
        {
            lock (lockServers)
            {
                var result = servers.Values.Select(serverEventsDTO => serverEventsDTO.AutoResetEvent).ToArray();
                return result;
            }
        }

        public void ResetEvents()
        {
            lock (lockServers)
            {
                foreach (var serverEventsDTO in servers.Values)
                {
                    serverEventsDTO.AutoResetEvent.Reset();
                }
            }
        }

        #endregion



        public void ProcessPacket(byte[] buffer, Socket handler)
        {
            SocketConnection = handler;
            Bandwith = buffer.Length;
            RecursivePacket(buffer, handler);
        }

        public void EndConnection(Socket handler)
        {
            if(!ServerSync)
                ServerInfo.Instance.RestoreBandwith(Bandwith);
            
            if(_fileContent != null)
            {
                _fileContent.Close();
                if (actionFileUpload && !uploadedOK)
                {
                    DeleteFile();
                }
            }
            
        }

        private void DeleteFile()
        {
            if (File.Exists(Settings.Default.FolderPath + _fileName))
            {
                File.Delete(Settings.Default.FolderPath + _fileName);
            }
        }


        internal void RecursivePacket(byte[] buffer, Socket handler)
        {
            if (buffer.Length + _currentBuffer.Length < 10)
            {
                IEnumerable<byte> bytes = _currentBuffer.Concat(buffer);
                _currentBuffer = bytes.ToArray();
            }
            else
            {
                IEnumerable<byte> bytes = _currentBuffer.Concat(buffer);
                byte[] tempArray = bytes.ToArray();

                UTF8Encoding decoder = new UTF8Encoding();
                int dataLength = int.Parse(decoder.GetString(tempArray, 5, 5));
                if (tempArray.Length < (dataLength + 10))
                {
                    _currentBuffer = tempArray;
                }
                else if (tempArray.Length >= (dataLength + 10))
                {
                    byte[] dataInfo = tempArray.Take(dataLength + 10).ToArray();
                    ProcessComand(dataInfo, handler);
                    _currentBuffer = new byte[0];
                    if (tempArray.Length > (dataLength + 10) && ((byte)tempArray.GetValue(dataLength + 10)) != 0)
                    {
                        RecursivePacket(tempArray.Skip(dataLength + 10).ToArray(), handler);
                    }
                }
            }
        }

        private void ProcessComand(byte[] bufferToProcess, Socket handler)
        {
            UTF8Encoding decoder = new UTF8Encoding();
            string header = decoder.GetString(bufferToProcess, 0, 3);
            string cmd = decoder.GetString(bufferToProcess, 3, 2);
            int dataLength = int.Parse(decoder.GetString(bufferToProcess, 5, 5));

            int ancho = Convert.ToInt32(Bandwith) - 10;
            byte[] dataToSend = new byte[ancho];

            Commands command = (Commands)int.Parse(cmd);
            byte[] dataInfo;
            switch (command)
            {
                case Commands.UPLOAD_CLIENT_SERVER_FILE_INFORMATION:
                    if (header.Equals("REQ"))
                    {
                        string data = decoder.GetString(bufferToProcess, 10, dataLength);
                        _md5 = Utilidades.ObtenerInformacion("MD5", data, ';');
                        _fileName = Utilidades.ObtenerInformacion("FILENAME", data, ';');
                        bool exists = ServerInfo.Instance.ExistsFile(_md5, _fileName);
                        ServerClientFileInfoResponse serverClientFileInfoResponse = new ServerClientFileInfoResponse(exists);
                        ICollection<ServerDTO> serverList = ServerInfo.Instance.GetServers();
                        foreach (ServerDTO serverDto in serverList)
                        {
                            try
                            {
                                TcpServerPacketProcessor tcpServerPacketProcessor = new TcpServerPacketProcessor();
                                Socket socket = TCPClient.ConnectToServer(serverDto.Ip.Port, serverDto.Ip.Address, tcpServerPacketProcessor,
                                                          Settings.Default.ClientMaxBandwidth);
                                AutoResetEvent eventAR = AddServer(serverDto.Ip, socket).AutoResetEvent;
                                tcpServerPacketProcessor.EventAutoReset = eventAR;
                                ServerServerSendInformation serverServerSendInformation = new ServerServerSendInformation(_md5,_fileName);
                                TCPClient.SendCommand(socket, serverServerSendInformation);
                            }
                            catch (Exception)
                            {
                                ServerInfo.Instance.RemoveFromServer(serverDto.Ip);
                            }
                        }
                        if (serverList.Count > 0 )
                        {
                            WaitHandle.WaitAll(GetServersAutoResetEvents(), 10000);
                        }

                        TCPClient.SendCommand(handler, serverClientFileInfoResponse);
                        actionFileUpload = true;
                    }
                    break;
                case Commands.UPLOAD_CLIENT_SERVER_FILE_INIT:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent = new FileStream(Settings.Default.FolderPath + _fileName, FileMode.Create, FileAccess.Write);
                            _fileContent.Write(dataInfo, 0, dataLength);

                            ResetEvents();
                            foreach (ServerEventsDTO serverEventsDTO in servers.Values)
                            {
                                ServerServerSendFileIni serverServerSendFileIni = new ServerServerSendFileIni(dataInfo);
                                TCPClient.SendCommand(serverEventsDTO.Socket, serverServerSendFileIni);
                            }
                            if (servers.Values.Count > 0 )
                            {
                                WaitHandle.WaitAll(GetServersAutoResetEvents(), 10000);
                            }

                            TCPClient.SendCommand(handler, new UploadServerClientNextPacket());
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerClietFileUpoadFailed(ex.ToString()));
                        }
                    }
                    break;
                case Commands.UPLOAD_CLIENT_SERVER_FILE_CONTINUE:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent.Write(dataInfo, 0, dataLength);

                            ResetEvents();
                            foreach (ServerEventsDTO serverEventsDTO in servers.Values)
                            {
                                ServerServerSendFileContinue serverServerSendFileContinue = new ServerServerSendFileContinue(dataInfo);
                                TCPClient.SendCommand(serverEventsDTO.Socket, serverServerSendFileContinue);
                            }
                            if (servers.Values.Count > 0)
                            {
                                WaitHandle.WaitAll(GetServersAutoResetEvents(), 10000);
                            }

                            TCPClient.SendCommand(handler, new UploadServerClientNextPacket());
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerClietFileUpoadFailed(ex.ToString()));
                        }
                    }
                    break;

                case Commands.UPLOAD_CLIENT_SERVER_FILE_END:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent.Write(dataInfo, 0, dataLength);
                            _fileContent.Flush(true);
                            _fileContent.Close();
                            if (Utilidades.GetMd5HashFromFile(Settings.Default.FolderPath + _fileName) == _md5)
                            {
                                UploadServerClientFileReceived uploadServerClientFileReceived = new UploadServerClientFileReceived();

                                ResetEvents();
                                foreach (ServerEventsDTO serverEventsDTO in servers.Values)
                                {
                                    ServerServerSendFileEnd serverServerSendFileEnd = new ServerServerSendFileEnd(dataInfo);
                                    TCPClient.SendCommand(serverEventsDTO.Socket, serverServerSendFileEnd);
                                }
                                if (servers.Values.Count > 0)
                                {
                                    WaitHandle.WaitAll(GetServersAutoResetEvents(), 10000);
                                }

                                TCPClient.SendCommand(handler, uploadServerClientFileReceived);
                                ServerInfo.Instance.AddFile(_md5, _fileName);
                                uploadedOK = true;
                            }
                            else
                            {
                                TCPClient.SendCommand(handler, new ServerClietFileUpoadFailed("Error en la subida: El MD5 del archivo no coincide."));
                                DeleteFile();
                            }

                        }
                        catch (SocketException ex)
                        {
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerClietFileUpoadFailed(ex.ToString()));
                        }
                    }
                    break;
                case Commands.DOWNLOAD_CLIENT_SERVER_INFORMATION_RESPONSE:
                    //dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("RES"))
                    {
                        string data = decoder.GetString(bufferToProcess, 10, dataLength);
                        _fileName = Utilidades.ObtenerInformacion("FILENAME", data, ';');
                        int startBytes = int.Parse(Utilidades.ObtenerInformacion("BYTES", data, ';'));

                        fileLength = (int)(_fileContent.Length - startBytes);

                        _fileContent = File.OpenRead(Settings.Default.FolderPath + _fileName);

                        if(startBytes>0)
                            _fileContent.Read(new byte[startBytes], 0, startBytes);

                        totalPaquetes = (int)Math.Ceiling(fileLength / (decimal)ancho);
                        _fileContent.Read(dataToSend, 0, ancho);
                        DownloadServerClientFileInit downloadServerClientFileInit =
                                  new DownloadServerClientFileInit(dataToSend);
                        TCPClient.SendCommand(handler, downloadServerClientFileInit);
                        paqueteActual += 1;
                    }
                    break;
                case Commands.DOWNLOAD_CLIENT_SERVER_NEXT_PACKET:
                    try
                    {
                        
                        if (paqueteActual == (totalPaquetes - 1))
                        {
                            dataToSend = new byte[Convert.ToInt32(fileLength - (paqueteActual * ancho))];
                            _fileContent.Read(dataToSend, 0, dataToSend.Length);
                            DownloadServerClientFileEnd downloadServerClientFileEnd =
                            new DownloadServerClientFileEnd(dataToSend);
                            TCPClient.SendCommand(handler, downloadServerClientFileEnd);
                            _fileContent.Close();
                        }
                        else
                        {
                            _fileContent.Read(dataToSend, 0, ancho);
                            DownloadServerClientFileContinue downloadServerClientFileContinue =
                            new DownloadServerClientFileContinue(dataToSend);
                            TCPClient.SendCommand(handler, downloadServerClientFileContinue);
                        }
                        paqueteActual += 1;
                    }
                    catch (Exception ex)
                    {
                        _fileContent.Close();
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();
                    }
                    break;
                case Commands.DOWNLOAD_CLIENT_SERVER_FILE_RECEIVED:
                    if (header.Equals("RES"))
                    {
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();
                        ServerInfo.Instance.RestoreBandwith(Bandwith);
                    }
                    break;

                case Commands.SERVER_SERVER_SEND_INFORMATION:
                    if (header.Equals("REQ"))
                    {
                        ServerSync = true;
                        string data = decoder.GetString(bufferToProcess, 10, dataLength);
                        _md5 = Utilidades.ObtenerInformacion("MD5", data, ';');
                        _fileName = Utilidades.ObtenerInformacion("FILENAME", data, ';');
                        bool exists = ServerInfo.Instance.ExistsFile(_md5, _fileName);
                        ServerServerSendInformationResponse serverServerSendInformationResponse =
                                new ServerServerSendInformationResponse();
                        TCPClient.SendCommand(handler, serverServerSendInformationResponse);
                    }
                    break;
                case Commands.SERVER_SERVER_SEND_FILE_INI:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent = new FileStream(Settings.Default.FolderPath + _fileName, FileMode.Create, FileAccess.Write);
                            _fileContent.Write(dataInfo, 0, dataLength);
                            TCPClient.SendCommand(handler, new ServerServerSendFileNextPackage());
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerServerSendFileFailed());
                        }
                    }
                    break;
                case Commands.SERVER_SERVER_SEND_FILE_CONTINUE:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent.Write(dataInfo, 0, dataLength);
                            TCPClient.SendCommand(handler, new ServerServerSendFileNextPackage());
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerServerSendFileFailed());
                        }
                    }
                    break;
                case Commands.SERVER_SERVER_SEND_FILE_END:
                    dataInfo = bufferToProcess.Skip(10).ToArray();
                    if (header.Equals("REQ"))
                    {
                        try
                        {
                            _fileContent.Write(dataInfo, 0, dataLength);
                            _fileContent.Flush(true);
                            _fileContent.Close();
                            if (Utilidades.GetMd5HashFromFile(Settings.Default.FolderPath + _fileName) == _md5)
                            {
                                ServerServerSendFileReceived serverServerSendFileReceived = new ServerServerSendFileReceived();
                                TCPClient.SendCommand(handler, serverServerSendFileReceived);
                                ServerInfo.Instance.AddFile(_md5, _fileName);
                            }
                            else
                            {
                                DeleteFile();
                                TCPClient.SendCommand(handler, new ServerServerSendFileFailed());
                            }

                        }
                        catch (SocketException ex)
                        {
                        }
                        catch (Exception ex)
                        {
                            _fileContent.Close();
                            DeleteFile();
                            TCPClient.SendCommand(handler, new ServerServerSendFileFailed());
                        }
                    }

                    break;
            }

        }
    }
}
